package cn.geminis.cost.workflow.controller;

import cn.geminis.core.util.DateUtils;
import cn.geminis.cost.workflow.entity.WorkflowCost;
import cn.geminis.cost.workflow.entity.filter.WorkflowCostFilter;
import cn.geminis.cost.workflow.entity.filter.WorkflowTemplateFilter;
import cn.geminis.cost.workflow.entity.repository.WorkflowTemplateRepository;
import cn.geminis.data.jpa.PageQuery;
import cn.geminis.workflow.client.WorkflowClient;
import cn.geminis.workflow.client.filter.TaskFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Map;

public class WorkflowCostController {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private final WorkflowClient workflowClient;
    private final WorkflowTemplateRepository workflowTemplateRepository;

    @PostMapping("/compute")
    public void compute(
            @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @RequestParam LocalDateTime finishStartTime,
            @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @RequestParam LocalDateTime finishEndTime) {
        var hasNextPage = false;
        var parser = new SpelExpressionParser();
        var mapAccessor = new MapAccessor();
        var pageParameter = PageQuery.builder()
                .pageIndex(0)
                .pageSize(100)
                .build();
        var queryParameter = TaskFilter.builder()
                .endTimeBeforeOrEqual(finishEndTime)
                .endTimeAfterOrEqual(finishStartTime)
                .build();
        do {
            var result = workflowClient.tasks(pageParameter, queryParameter);

            result.getContent().forEach(task -> {
                var taskData = workflowClient.taskData(task.getId());
                var context = new StandardEvaluationContext(taskData);
                context.addPropertyAccessor(mapAccessor);
                this.workflowTemplateRepository
                        .findAll(
                                WorkflowTemplateFilter.builder()
                                        .processKey(task.getProcessDefinitionKey())
                                        .taskKey(task.getTaskDefinitionKey())
                                        .build()
                        )
                        .forEach(template -> {
                            var expression = parser.parseExpression(template.getTemplate());
                            var resultValue = expression.getValue(context);

                            var costValue = resultValue instanceof BigDecimal ? (BigDecimal) resultValue : new BigDecimal(resultValue.toString());
                            var cost = this.repository
                                    .findOne(
                                            WorkflowCostFilter.builder()
                                                    .processInstanceId(task.getProcessInstanceId())
                                                    .taskInstanceId(task.getId())
                                                    .templateId(template.getId())
                                                    .build()
                                    )
                                    .orElse(new WorkflowCost());
                            cost.setProcessDefinitionId(task.getProcessDefinitionId());
                            cost.setProcessDefinitionKey(task.getProcessDefinitionKey());
                            cost.setProcessInstanceId(task.getProcessInstanceId());
                            cost.setProcessName(getProcessName(task.getDescription()));
                            cost.setTaskDefinitionId(task.getTaskDefinitionId());
                            cost.setTaskDefinitionKey(task.getTaskDefinitionKey());
                            cost.setTaskInstanceId(task.getId());
                            cost.setTaskName(task.getName());
                            cost.setUsername(task.getAssignee());
                            cost.setStartTime(DateUtils.toLocalDateTime(task.getCreateTime()));
                            cost.setEndTime(DateUtils.toLocalDateTime(task.getEndTime()));
                            cost.setTemplate(template);
                            cost.setValue(costValue);
                            this.repository.save(cost);
                        });
            });

            pageParameter.setPageIndex(pageParameter.getPageIndex());
            hasNextPage = result.hasNext();
        } while (hasNextPage);
    }

    @SneakyThrows
    private String getProcessName(String json) {
        if (StringUtils.hasText(json)) {
            return (String) OBJECT_MAPPER.readValue(json, Map.class).get("processName");
        } else {
            return null;
        }
    }
}
